/******************************************************************************************
版权所有 2021-2021, Turix实验中心。保留所有权利。
文件名：main.c
作者：燕卫博    版本：V1.0    日期：2021.8.11
文件描述：
    程序包含两个分支：BGR图片检测程序分支和即时检测程序分支。
    【即时检测程序分支】初始化NNIE模块和MPP的VI(IMX307/GC2053)-VPSS(2Chn)~VO(HDMI)通路，
                        VPSS使能2路通道，高分辨率图像用于显示，低分辨率图像用于送NNIE预测。
                        依照预测结果在高分辨率图像上绘制检测框。
                        若编译时使能了H264_RTP_ENABLE宏开关，检测结果将同时被以H264-RTP方
                        式转发到远程端口。
    【BGR图片检测程序分支】初始化NNIE模块，仅检测单张320x320分辨率的BGR图像并将结果通过串
                           口终端打印到屏幕。
其它说明：
    9Hv2，个人。
    仅考虑适配YOLO-Fastest1.0-XL。
历史修改记录：
1. 2021-5-27:V1.0 燕卫博
创建。

2. 2021-8-11:V1.0 燕卫博
从9Hv1拷贝，仅考虑适配Hi3516DV300，单路检测，H264-RTP推流和HDMI送显示例，用作实验室公开。
******************************************************************************************/
//                                              ↓
//+------------------------------------------------------------------------------------------+
//|                                          工程自述
//+------------------------------------------------------------------------------------------+
//|      “9Hv2”是由当前工程首位开发者分配的标识代号，代指当前工程主体。
//|      9Hv2工程的目标任务为：基于9Hv1个人工程，仅适配Hi3516DV300，使用YOLO-Fastest网络实现
//|  实时检测和实时编码，并分离检测和编码逻辑。工程归属于代号为“WIT”的更高任务范围的衍生。
//|      本工程的性质为个人学习工程，因此仅实现基本功能，不考虑可扩展性和灵活配置的需求。实际
//|  使用时，不建议直接基于本工程做修改。
//|
//|      最简单的检测思路可参考SVP-NNIE例程中的RFCN示例流程。MPP将初始化单路VI并经由VPSS模块
//|  生成两条不同分辨率的图像处理通路，其中一路为用于显示的大分辨率图像，另一路为用于送往NNIE
//|  进行预测的小分辨率图像。考虑到图像拉抻可能对检测效果造成负面影响，工程中保持图像比例进行
//|  缩放(2M -> 320*180)，而后在装载图像到NNIE时使用标准灰色RGB(128,128,128)对源Blob空间进
//|  行对称填充。获取到网络输出结果后，再对坐标进行变换，使用VGS模块对大分辨率图像打Cover，最
//|  后送VO模块显示输出。
//|      上述程序结构便于理解，但因显示与预测处于同一过程，预测性能将影响显示帧数。因此为了实
//|  现满帧显示或编码，9Hv2选择抽帧送检的方式重新设计主程序，抽帧检测结果通过全局变量共享给主
//|  线程，以供显示、编码或后续处理所需。
//|      程序同时包含了送帧编码和RTP发送的示例，由于此功能是被追加的，仅用于功能实现示意，因
//|  此将相关参数直接写死在程序中而不考虑实现灵活配置——毕竟这个程序本来就是为了示例而生。
//|      工程中使用的wk文件从YOLO-Fastest1.0-XL网络在COCO数据集上的预训练权重转换而来，网络转
//|  换（Darknet2Caffe）的步骤、过程和资料参见WIT文档或其它网络资料。
//|
//|      本自述作为C&I Lab.实验室内部代码公开的许可凭证。
//+------------------------------------------------------------------------------------------+
//|                                           宏开关
//+------------------------------------------------------------------------------------------+
//|    ----------------------------------------
//|    NOTE:若使能H264_RTP_ENABLE宏定义，检测结
//|         果将被同时转发到远程端口。
//|    ----------------------------------------
//|    #define H264_RTP_ENABLE
//+------------------------------------------------------------------------------------------+
//|                                         头文件包含
//+------------------------------------------------------------------------------------------+
/*|*/  #include <stdio.h>
/*|*/  #include <stdlib.h>
/*|*/  #include <stdint.h>
/*|*/  #include <stdbool.h>
/*|*/  #include <signal.h>
/*|*/  #include <unistd.h>
/*|*/  #include <pthread.h>
/*|*/  #include <sys/time.h>
/*|*/  #include <arpa/inet.h>
//|
/*|*/  #include "hi_comm_ive.h"
/*|*/  #include "hi_comm_vpss.h"
/*|*/  #include "hi_comm_venc.h"
/*|*/  #include "mpi_vpss.h"
/*|*/  #include "mpi_venc.h"
/*|*/  #include "mpi_vo.h"
//|
/*|*/  #include "L_MPP40_sys.h"
/*|*/  #include "L_MPP40_vi.h"
/*|*/  #include "L_MPP40_vpss.h"
/*|*/  #include "L_MPP40_vo.h"
/*|*/  #include "L_MPP40_vgs.h"
/*|*/  #include "L_SVP_NNIE_model.h"
/*|*/  #include "L_SVP_IVE_csc.h"
//|
/*|*/  #ifdef H264_RTP_ENABLE
/*|*/  #include "L_MPP40_venc.h"
/*|*/  #include "L_RTP.h"
/*|*/  #endif
//+------------------------------------------------------------------------------------------+
//|                                          全局变量
//+------------------------------------------------------------------------------------------+
//|    主程序循环标志位
/*|*/  static bool flag_main_loop = true;
//|
//|    ----------------------------------------
//|    显示/检测线程共享与同步相关
//|    ----------------------------------------
/*|*/  static bool                         flag_rect_ready = false;
/*|*/  static pthread_mutex_t              bbox_mutex = PTHREAD_MUTEX_INITIALIZER;
/*|*/  static pthread_t                    detect_tid;
/*|*/  static IVE_IMAGE_S                  stOutputRGB888Image = {0};
/*|*/  static SVP_NNIE_YOLO_OUTPUT_BBOX_S *pBboxBuf = NULL;
/*|*/  static int                          BboxNum = 0;
/*|*/  static VGS_RECT                     rect[MAX_DETECTION_NUM] = {0};
//|
/*|*/  #ifdef H264_RTP_ENABLE
//|    ----------------------------------------
//|    编码线程相关
//|    ----------------------------------------
/*|*/  static pthread_t                    encode_tid;
//|
/*|*/  static VENC_PARA_S stVENCpara=
/*|*/  {
/*|*/      .VencChn=0,
/*|*/      .enPayLoad=PT_H264,
/*|*/      .enSize=PIC_1080P,
/*|*/      .enGopMode=VENC_GOPMODE_NORMALP,
/*|*/      .u32Gop=30,
/*|*/      .enRcMode=RC_CBR,
/*|*/      .u32FrameRate=30,
/*|*/      .u32Profile=0,
/*|*/      .s32FrameNumber=-1,
/*|*/  };
//|
//|    ----------------------------------------
//|    推流设置相关
//|    为方便增删，将推流信息作为全局变量
//|    ----------------------------------------
/*|*/  static char rtp_remote_ip[] = "169.254.134.37";
/*|*/  static char rtp_remote_port[] = "1118";
/*|*/  static L_STRUCT_RTPSESSION RTPSession=
/*|*/  {
/*|*/      .flag_inited = 0,
/*|*/  };
/*|*/  #endif
//+------------------------------------------------------------------------------------------+

#ifdef H264_RTP_ENABLE
//+------------------------------------------------------------------------------------------+
//|  函数名称：stream_process
//|  功能描述：编码码流处理
//|  参数说明：VENC通道号，码流包结构体
//|  返回值说明：无
//|  备注：
//+------------------------------------------------------------------------------------------+
static void stream_process(int VencChn, VENC_STREAM_S *pstStream)
{
    int packagelength;
    static float len_per_second = 0;
    static int framecount = 0;
    
    if(VencChn == stVENCpara.VencChn)
    {
        for(int i = 0; i < pstStream->u32PackCount; i++)
        {
            packagelength = pstStream->pstPack[i].u32Len - pstStream->pstPack[i].u32Offset;
            len_per_second += packagelength;
            
            RTPSession.pData      = pstStream->pstPack[i].pu8Addr + pstStream->pstPack[i].u32Offset;
			RTPSession.DataLength = packagelength;
			RTPSession.TimeStamp  = pstStream->pstPack[i].u64PTS;
			RTPSession.DataType   = RTP_H264;
			L_RTP_Send(&RTPSession);
        }
        
        framecount++;
        if(framecount % 30 == 0)
        {
            printf("[ENCODE -INFO]RC:%f Kbps\n", len_per_second/128);
            len_per_second = 0;
            framecount = 0;
        }
    }
}

//+------------------------------------------------------------------------------------------+
//|  函数名称：encode_process
//|  功能描述：编码处理线程
//|  参数说明：无
//|  返回值说明：无
//|  备注：
//+------------------------------------------------------------------------------------------+
static void *encode_process(void *p)
{
    L_VENC_GetStream(&flag_main_loop, stream_process);
    
    pthread_exit(NULL);
}

#endif

//+------------------------------------------------------------------------------------------+
//|  函数名称：detect_process
//|  功能描述：检测处理线程
//|  参数说明：无
//|  返回值说明：无
//|  备注：
//+------------------------------------------------------------------------------------------+
static void *detect_process(void *p)
{
    HI_S32 s32Ret = HI_SUCCESS;
    VIDEO_FRAME_INFO_S stLittleFrameInfo  = {0};
    struct timeval tvstart, tvend;
    int detect_fps = 0;
    long runningtime = 0;
    
    gettimeofday(&tvstart, NULL);
    while(flag_main_loop)
    {
        //------------------------------------------------------------
        // 获取小分辨率图像
        // NOTE:VPSS两通道的初始化见其封装文件。通道2为待画框和送显的
        //      原图；通道1为待检测的缩略图。
        // ATTENTION:本工程中，缩略图大小需定为320 * 180。
        //------------------------------------------------------------
        s32Ret = HI_MPI_VPSS_GetChnFrame(0, 1, &stLittleFrameInfo, 1000);
        if(s32Ret != HI_SUCCESS){printf("[MAIN-ERROR]HI_MPI_VPSS_GetChnFrame[1] failed with %#x!\n", s32Ret);break;}
        
        //------------------------------------------------------------
        // 图像像素格式转换
        // NOTE:网络模型需要的图像像素格式为U8C3，但VPSS输出的图像像素
        //      仅支持YVU-SP420/YVU-SP422/YUV-SP420/YUV-SP422/YUV400。
        //      因此需进一步将YVU/YUV图像转换为RGB格式。
        // NOTE:IVE仅能将图像转为RGB而非BGR。
        //------------------------------------------------------------
        s32Ret = L_SVP_IVE_CSC(&stLittleFrameInfo, &stOutputRGB888Image);
        if(s32Ret != 0){printf("[MAIN-ERROR]L_SVP_IVE_CSC failed with %#x!\n", s32Ret);break;}
        
        //------------------------------------------------------------
        // 装载待检测图像
        // NOTE:对待检测图像的预处理操作由装载函数完成。
        // ATTENTION:这里装载的图像大小需为320 * 180，否则需修改装载函
        //           数中的padding操作。
        //------------------------------------------------------------
        s32Ret = L_SVP_NNIE_LoadData(NULL, &stOutputRGB888Image);
        if(s32Ret != 0){printf("[MAIN-ERROR]L_SVP_NNIE_LoadData failed with %#x!\n", s32Ret);break;}
        
        //------------------------------------------------------------
        // NNIE前向计算
        //------------------------------------------------------------
        s32Ret = L_SVP_NNIE_Forward();
        if(s32Ret != 0){printf("[MAIN-ERROR]L_SVP_NNIE_Forward failed with %#x!\n", s32Ret);break;}
        
        //------------------------------------------------------------
        // 获取检测结果
        //------------------------------------------------------------
        pthread_mutex_lock(&bbox_mutex);
        L_SVP_NNIE_GetResult(&pBboxBuf, &BboxNum);
        
        if(BboxNum != 0)
        {
            /* 统计框信息并填充rect结构 */
            for(int i = 0; i < BboxNum; i++)
            {
                /* Bbox坐标以图像左上为原点，6为宽高缩放倍数（1920/320） */
                int32_t trans_Xmin = 6 * SVP_NNIE_MAX(pBboxBuf[i].u32Xmin, 0);
                int32_t trans_Ymin = 6 * SVP_NNIE_MAX(pBboxBuf[i].u32Ymin - 70, 0);
                int32_t trans_Xmax = 6 * SVP_NNIE_MIN(pBboxBuf[i].u32Xmax, 320);
                int32_t trans_Ymax = 6 * SVP_NNIE_MIN(pBboxBuf[i].u32Ymax - 70, 180);
                
                /* 左上 */
                rect[i].astPoint[0].s32X = trans_Xmin;
                rect[i].astPoint[0].s32Y = trans_Ymin;
                
                /* 左下 */
                rect[i].astPoint[1].s32X = trans_Xmin;
                rect[i].astPoint[1].s32Y = trans_Ymax;
                
                /* 右下 */
                rect[i].astPoint[2].s32X = trans_Xmax;
                rect[i].astPoint[2].s32Y = trans_Ymax;
                
                /* 右上 */
                rect[i].astPoint[3].s32X = trans_Xmax;
                rect[i].astPoint[3].s32Y = trans_Ymin;
            }
        }
        pthread_mutex_unlock(&bbox_mutex);
        flag_rect_ready = true;
        
        //------------------------------------------------------------
        // 释放图像
        //------------------------------------------------------------
        HI_MPI_VPSS_ReleaseChnFrame(0, 1, &stLittleFrameInfo);
        
        //------------------------------------------------------------
        // 处理帧率统计
        //------------------------------------------------------------
        detect_fps++;
        gettimeofday(&tvend, NULL);
        runningtime = (1000000 * tvend.tv_sec + tvend.tv_usec) - (1000000 * tvstart.tv_sec + tvstart.tv_usec);
        if(runningtime > 1000000)
        {
            printf("[DETECT -INFO]Detect  FPS:%d\n", detect_fps);
            detect_fps = 0;
            gettimeofday(&tvstart, NULL);
        }
    }
    
    pthread_exit(NULL);
}

//+------------------------------------------------------------------------------------------+
//|  函数名称：Usage
//|  功能描述：使用说明
//|  参数说明：无
//|  返回值说明：无
//|  备注：
//+------------------------------------------------------------------------------------------+
static void Usage(void)
{
    printf("Usage:\n");
    printf("Adaptation:Hi3516DV300\n");
    printf("You can call program like this:\n");
    printf("    1) ./program\n");
    printf("       Only show this Usage.\n");
    printf("    2) ./program wk-filename\n");
    printf("       The program will get the data from VI, after predicting by YOLO-Fastest, the result will be displayed on HDMI.\n");
    printf("       If RTP is enabled, the detection result will be forwarded to the remote port at the same time.\n");
    printf("    3) ./program wk-filename bgr-filename\n");
    printf("       The program will only predict the BGR image and print the result on the console.\n");
    printf("Author:Linyar Version:9Hv2 E-mail:WindForest@yeah.net\n");
    printf("Infomation:9Hv2 belongs to WIT-Extend project.\n");
    printf("[NOTICE]Input Ctrl+C to stop program.\n\n");
}

//+------------------------------------------------------------------------------------------+
//|  函数名称：sig_handle
//|  功能描述：信号处理
//|  参数说明：信号值
//|  返回值说明：无
//|  备注：
//+------------------------------------------------------------------------------------------+
static void sig_handle(int signo)
{
	if(SIGINT == signo || SIGTERM == signo)
	{
        flag_main_loop = false;
	}
}

//+------------------------------------------------------------------------------------------+
//|  函数名称：main
//|  功能描述：主函数
//|  参数说明：当无传参时，程序仅打印使用说明；
//|            当传递1个参数时，该参数应为wk文件路径；
//|            当传递2个或以上参数时，第2个参数为待预测的bgr图像，程序仅打印该图像的预测结果而
//|            不调用MPP和摄像头。
//|  返回值说明：略
//|  备注：
//+------------------------------------------------------------------------------------------+
int main(int argc, const char *argv[])
{
    HI_S32 s32Ret = HI_SUCCESS;
    VIDEO_FRAME_INFO_S stBigFrameInfo = {0};
    struct timeval tvstart, tvend;
    int display_fps = 0;
    long runningtime = 0;
    
    signal(SIGINT,  sig_handle);
    signal(SIGTERM, sig_handle);
    
    Usage();
    
    if(argc == 1)exit(0);
    
    //------------------------------------------------------------
    // MPP系统初始化
    //------------------------------------------------------------
    L_MPP_SYS_Init();
    
    //------------------------------------------------------------
    // SVP初始化
    //------------------------------------------------------------
    L_SVP_NNIE_Init(argv[1]);
    L_SVP_IVE_CSC_Init(&stOutputRGB888Image);
    
    //------------------------------------------------------------
    // 根据传入参数决定应用程序运行路径
    //------------------------------------------------------------
    if(argc > 2)
    {
        printf("[MAIN-INFO]NNIE will predict the image from the file.\n");
        
        L_SVP_NNIE_LoadData(argv[2], NULL);
            gettimeofday(&tvstart, NULL);
        L_SVP_NNIE_Forward();
            gettimeofday(&tvend, NULL);
            runningtime = (1000000 * tvend.tv_sec + tvend.tv_usec) - (1000000 * tvstart.tv_sec + tvstart.tv_usec);
            printf("[MAIN-TEST]L_SVP_NNIE_Forward takes %ldus\n", runningtime);
        
            gettimeofday(&tvstart, NULL);
        L_SVP_NNIE_GetResult(&pBboxBuf, &BboxNum);
            gettimeofday(&tvend, NULL);
            runningtime = (1000000 * tvend.tv_sec + tvend.tv_usec) - (1000000 * tvstart.tv_sec + tvstart.tv_usec);
            printf("[MAIN-TEST]L_SVP_NNIE_GetResult takes %ldus\n", runningtime);
        
            gettimeofday(&tvstart, NULL);
        printf("[MAIN-TEST]Get Bbox(es):\n");
        if(BboxNum != 0)
        {
            for(int i = 0; i < BboxNum; i++)
            {
                printf("\tXmin:%d Ymin:%d Xmax:%d Ymax:%d Prob:%f ClassIndex:%d\n",
                                                                        pBboxBuf[i].u32Xmin,
                                                                        pBboxBuf[i].u32Ymin,
                                                                        pBboxBuf[i].u32Xmax,
                                                                        pBboxBuf[i].u32Ymax,
                                                                        pBboxBuf[i].f32ObjectProb,
                                                                        pBboxBuf[i].u32ClassIndex);
            }
        }
            gettimeofday(&tvend, NULL);
            runningtime = (1000000 * tvend.tv_sec + tvend.tv_usec) - (1000000 * tvstart.tv_sec + tvstart.tv_usec);
            printf("[MAIN-TEST]GetResults takes %ldus\n", runningtime);
        
        goto END_NNIE;
    }
    
    //------------------------------------------------------------
    // MPP业务初始化
    // NOTE:VI(IMX307/GC2053)-VPSS(2Chn)~VO(HDMI)
    //------------------------------------------------------------
    if(L_VI_Init(NULL) < 0)goto END_VI;
    if(L_VPSS_Init()   < 0)goto END_VPSS;
#ifdef H264_RTP_ENABLE
    if(L_VENC_CreateChnForVideo(&stVENCpara) < 0)goto END_VENC;
#endif
    if(L_VO_Init()     < 0)goto END_VO;
    
    printf("[MAIN-INFO]NNIE will predict the image from the sensor.\n");
    
    //------------------------------------------------------------
    // 创建检测线程
    //------------------------------------------------------------
    if(pthread_create(&detect_tid, NULL, detect_process, NULL) != 0)
    {
        printf("[MAIN-ERROR]Create detect thread failed!\n");
        flag_main_loop = false;
        goto END_VO;
    }
    
#ifdef H264_RTP_ENABLE
    //------------------------------------------------------------
    // 创建编码线程
    //------------------------------------------------------------
    if(pthread_create(&encode_tid, NULL, encode_process, NULL) != 0)
    {
        printf("[MAIN-ERROR]Create encode process thread failed!\n");
        flag_main_loop = false;
        goto END_DETECT;
    }

    //------------------------------------------------------------
    // 创建RTP会话
    //------------------------------------------------------------
	inet_aton(rtp_remote_ip, (struct in_addr *)&(RTPSession.u32DestIP));
	RTPSession.DestPort = atoi(rtp_remote_port);
    if(L_RTP_CreateSession(&RTPSession) != 0)
	{
		printf("[MAIN-ERROR]Create RTP session failed!\n");
        flag_main_loop = false;
		goto END_ENCODE;
	}
    printf("[MAIN-INFO]RTP[%s:%s] startup.", rtp_remote_ip, rtp_remote_port);
#endif
    
    gettimeofday(&tvstart, NULL);
    while(flag_main_loop)
    {
        //------------------------------------------------------------
        // 获取原分辨率图像
        // NOTE:VPSS两通道的初始化见其封装文件。通道2为待画框和送显的
        //      原图；通道1为待检测的缩略图。
        // ATTENTION:本工程中，缩略图大小需定为320 * 180。
        //------------------------------------------------------------
        s32Ret = HI_MPI_VPSS_GetChnFrame(0, 2, &stBigFrameInfo, 1000);
        if(s32Ret != HI_SUCCESS){printf("[MAIN-ERROR]HI_MPI_VPSS_GetChnFrame[2] failed with %#x!\n", s32Ret);break;}
        
        //------------------------------------------------------------
        // 在原图上按比例绘制检测框
        // NOTE:绘制检测框前，需对检测结果进行变换，主要包括对坐标的变
        //      换，因为数据装载时的padding操作会将原图填充为320 * 320
        //      的图像。
        //------------------------------------------------------------
        pthread_mutex_lock(&bbox_mutex);
        if(BboxNum != 0 && flag_rect_ready)
        {
            /* 将框信息送VGS打Cover */
            s32Ret = L_VGS_AddCover(&stBigFrameInfo, rect, BboxNum, 0x000000FF);
            if(s32Ret != HI_SUCCESS)printf("[MAIN-ERROR]L_VGS_AddCover failed with %#x!\n", s32Ret);
        }
        pthread_mutex_unlock(&bbox_mutex);
        
        //------------------------------------------------------------
        // 视频帧送显
        // NOTE:送显示到视频层0-通道0，非阻塞，该视频层送往HDMI。
        //------------------------------------------------------------
        s32Ret = HI_MPI_VO_SendFrame(0, 0, &stBigFrameInfo, 0);
        if(s32Ret != HI_SUCCESS){printf("[MAIN-ERROR]HI_MPI_VO_SendFrame[Layer0] failed with %#x!\n", s32Ret);continue;}
        
#ifdef H264_RTP_ENABLE
        s32Ret = HI_MPI_VENC_SendFrame(stVENCpara.VencChn, &stBigFrameInfo, 0);
        if(s32Ret != HI_SUCCESS){printf("[MAIN-ERROR]HI_MPI_VENC_SendFrame failed with %#x!\n", s32Ret);continue;}
#endif
        
        //------------------------------------------------------------
        // 释放图像
        //------------------------------------------------------------
        HI_MPI_VPSS_ReleaseChnFrame(0, 2, &stBigFrameInfo);
        
        //------------------------------------------------------------
        // 显示帧率统计
        //------------------------------------------------------------
        display_fps++;
        gettimeofday(&tvend, NULL);
        runningtime = (1000000 * tvend.tv_sec + tvend.tv_usec) - (1000000 * tvstart.tv_sec + tvstart.tv_usec);
        if(runningtime > 1000000)
        {
            printf("[DISPLAY-INFO]Display FPS:%d\n", display_fps);
            display_fps = 0;
            gettimeofday(&tvstart, NULL);
        }
    }
    
    //------------------------------------------------------------
    // MPP业务去初始化
    //------------------------------------------------------------
#ifdef H264_RTP_ENABLE
    L_RTP_DestorySession(&RTPSession);
END_ENCODE:
    pthread_join(encode_tid, NULL);
END_DETECT:
#endif

    pthread_join(detect_tid, NULL);
END_VO:
    L_VO_DeInit();
    
#ifdef H264_RTP_ENABLE
END_VENC:
    L_VENC_DeInit(stVENCpara.VencChn);
#endif

END_VPSS:
    L_VPSS_DeInit();
END_VI:
    L_VI_DeInit();
    
    //------------------------------------------------------------
    // SVP去初始化
    //------------------------------------------------------------
END_NNIE:
    L_SVP_NNIE_DeInit();
    L_SVP_IVE_CSC_DeInit(&stOutputRGB888Image);
    
    //------------------------------------------------------------
    // MPP系统退出
    //------------------------------------------------------------
    L_MPP_SYS_DeInit();
    
    //------------------------------------------------------------
    // 其它退出操作
    //------------------------------------------------------------
    
    return 0;
}
